/*
 * Copyright (C) 2000-2008 Jens Nixdorf and various contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.mopore.jat.board;

import java.awt.AWTException;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BoardFeature implements Runnable, IScreenOperator, ICutable, IMarkable {

    private boolean wanTolive = true;

    private Frame frame;

    private BufferedImage screenImage;

    private BufferStrategy strategy;

    private int screenWidth;

    private int screenHeight;

    private Cutout cutout;

    private MarkingQueue queue;

    private final Log log = LogFactory.getLog(getClass().getName());

    private final Color cutoutColor;

    private final CutoutPainter cutoutPainter;

    private final HazePainter hazePainter;

    private boolean operating;

    private final MarkingPainter markingPainter;

    private boolean finishMarking;

    private List<Future<?>> waitList;

    private ExecutorService executerService;

    private long strategyCreationTime = 0;

    private long screenCapturingTime = 0;

    private long settingFullscreenTime = 0;

    private ButtonBarPainter buttonBarPainter;

    private MouseStrokePreview mouseStrokePreview;

    public BoardFeature() throws AWTException {

        executerService = Executors.newSingleThreadExecutor();
        Robot robot = new Robot();

        this.queue = new MarkingQueue();
        long captureStart = System.currentTimeMillis();
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        screenWidth = (int) toolkit.getScreenSize().getWidth();
        screenHeight = (int) toolkit.getScreenSize().getHeight();

        Rectangle rectangle = new Rectangle(0, 0, screenWidth, screenHeight);
        screenImage = robot.createScreenCapture(rectangle);
        screenCapturingTime = System.currentTimeMillis() - captureStart;

        long fullScreenStart = System.currentTimeMillis();

        // Determine if page flipping is supported
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();

        // Create a window for full-screen mode
        frame = new MyFrame(gd.getDefaultConfiguration(), this, this, this);
        frame.setUndecorated(true);

        // Configure the window so that a mouse click will exit full-screen mode

        try {
            frame.setSize(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight());
            frame.setLocation(0, 0);
            frame.setVisible(true);

            frame.requestFocus();
            settingFullscreenTime = System.currentTimeMillis() - fullScreenStart;
            long strategeyStart = System.currentTimeMillis();
            frame = StrategyChooser.loadDefaultStrategy(frame);
            strategy = frame.getBufferStrategy();
            strategyCreationTime = System.currentTimeMillis() - strategeyStart;

        } catch (Throwable e) {
            log.error( "an error occured", e );
        }

        this.operating = false;
        this.finishMarking = false;
        this.waitList = new LinkedList<Future<?>>();
        this.buttonBarPainter = new ButtonBarPainter(screenImage, this.queue);
        this.cutoutColor = new Color(255, 0, 0);
        this.cutoutPainter = new CutoutPainter(cutoutColor);
        this.hazePainter = new HazePainter(screenWidth, screenHeight);
        this.markingPainter = new MarkingPainter();
        this.mouseStrokePreview = new MouseStrokePreview();

        BoardAdapter adapter = new BoardAdapter(this, this, this, this.queue,
                this.buttonBarPainter, this.mouseStrokePreview);
        frame.addMouseListener(adapter);
        frame.addMouseMotionListener(adapter);
        frame.addKeyListener(adapter);
        frame.addMouseWheelListener(adapter);
        frame.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                boolean escape = (e.getKeyCode() == KeyEvent.VK_ESCAPE);
                if (escape) {
                    log.info("Escape pressed...");
                    // VisualCalibtration.this.release();
                }
            }
        });
    }

    private void paint(Graphics2D g) {

        final Composite origComposite = g.getComposite();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.drawImage(screenImage, null, 0, 0);

        hazePainter.paintHaze(g, origComposite);

        for (Marking marking : this.queue.getMarkingsToPaint()){
            markingPainter.paintMarking(g, origComposite, marking);
        }

        this.mouseStrokePreview.paint(g, origComposite);

        if (cutout != null){
            cutoutPainter.paintCutout(g, origComposite, cutout);
        }

        // this.watermarkPainter.paintWatermark(g, origComposite, screenImage);
        this.buttonBarPainter.paint(g, origComposite, screenImage);
    }

    private void mark() {
        if (this.queue.isWriteMarkings()) {
            log.debug("marking unbuffered marker to the screenimage now...");

            // copy the current marking to the screenimage;
            Graphics2D g = screenImage.createGraphics();
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            Composite origComposite = g.getComposite();
            markingPainter.paintMarking(g, origComposite, this.queue.getMarkingToWrite());
            g.dispose();

            // all done;
            finishMarking = false;
            log.debug("marking painted.");

            this.queue.setWriteMarking(false);
        }
    }

    public void cut(Cutout cutout) {
        this.cutout = cutout;
    }

    public void setOperating(boolean operating) {
        this.operating = operating;
    }

    /**
     * 
     */
    private void release() {
        frame.dispose();

        for (Marking marking : this.queue.getMarkingsToPaint()){
            AlphaComposite alphaComposite = 
            	AlphaComposite.getInstance(AlphaComposite.CLEAR);
            markingPainter.paintMarking( screenImage.createGraphics(), 
            		alphaComposite, marking);
        }

        Rectangle cutOutRectangle = cutout.toRectangle();
        ClipboardCallable callable = new ClipboardCallable( screenImage, 
        		cutOutRectangle);
        Future<Class<Void>> f = executerService.submit(callable);
        waitList.add(f);

        log.info("Checking waitlist...");
        int index = 1;

        for (Future<?> future : waitList) {
            log.debug("checking task number " + index + "...");
            try {
                if (future.isDone()) {
                    log.debug("task is allready finished.");
                } else {
                    log.debug("will wait 1 seconds");
                    future.get(1, TimeUnit.SECONDS);

                    if (future.isDone()) {
                        log.debug("task is done");
                    } else {
                        log.info("cancelling tasks...");
                        future.cancel(true);
                        log.info("task cancelled...");
                    }
                }
            } catch (Exception e) {
                log.error("task could not be finished, but will exit anyway", e);
            }
            index++;
        }

        log.info("stopping executer service...");
        executerService.shutdown();
        boolean ended = executerService.isTerminated();
        log.info("executerservice is terminated: " + ended);
        log.info("finished");
    }

    public void mark(Marking marking) {
        this.queue.addMarking(marking);
    }

    public void finishMarking() {
        finishMarking = true;
    }

    public void finishCutting() {
        cutout.cuttingFinished();
    }

    public void run() {

        log.info("Presenting perfomance number...");
        log.info("screenCapturingTime: " + screenCapturingTime);
        log.info("settingFullscreenTime: " + settingFullscreenTime);
        log.info("strategyCreationTime: " + strategyCreationTime);

        // Draw loop
        while (wanTolive) {
            // Get screen size

            // Get graphics context for drawing to the window
            Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
            paint(g);

            // done drawing
            g.dispose();

            // flip the back buffer to the screen
            strategy.show();

            if (!operating && !this.mouseStrokePreview.isActive()) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    log.error("can not sleep", e);
                }
            }

            if (cutoutPainter.isFinished()) {
                wanTolive = false;
            }

            if (finishMarking) {
                mark();
            }
        }
        log.debug("no reason to live anymore...");
        release();
    }

    public static void main(String[] args) throws AWTException {
        Runnable r = new BoardFeature();
        Thread t = new Thread(r);
        t.start();
    }
}
